package com.example.demo.utils.excelUtil;

import com.example.demo.utils.excelUtil.annotation.ExcelProperties;
import lombok.Data;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.RegionUtil;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * ClassName: ExcelUtils
 * Description:
 * Author: lxw
 * Date: 2025/7/15 15:58
 * History:
 * <author>          <time>          <version>          <desc>
 * lxw         2025/7/15 15:58   @Version 1.0        描述
 */
public abstract class AbstractExcelExportManager<T, K extends BaseExportExcelRequest> implements ExportManager<K> {

    private final Logger LOGGER = LoggerFactory.getLogger(AbstractExcelExportManager.class);

    public AbstractExcelExportManager() {

    }

    @Override
    public void exportDetails(K request) {
        try {
            // 初始化配置，比如获取规格配置之类的操作，子类自定义
            initConfig(request);
            // 获取源数据（需要导出的数据列表）
            T data = getSourceData(request);
            // 前置判空
            if (Objects.isNull(data) || (data instanceof Collection && CollectionUtils.isEmpty((Collection<?>) data))) {
                LOGGER.warn("getSourceData failed: return value is null or empty, request={}", request);
                return;
            }
            // 处理数据并返回workBook
            Workbook workbook = processToWorkBook(request, data, this::createNormalExcelReport);
            // 后置处理
            postEvent(data);
            // 执行导出
            doExport(workbook, request.getOutputStream());
        } catch (IOException e) {
            LOGGER.error("导出失败，请检查原因, request={}", request);
            throw new RuntimeException("导出excel出现IOException....");
        } catch (Exception e) {
            LOGGER.error("AbstractExcelExportManager.exportDetails error，请检查原因, request={}", request);
            throw new RuntimeException("exportDetails出现异常，导出失败");
        }
    }

    /**
     * 获取数据列表
     */
    protected abstract T getSourceData(K k);

    /**
     * 初始化配置
     *
     * @param request
     */
    protected void initConfig(K request) {
        // default do nothing ，可由子类扩展重写
    }

    protected void postEvent(T data) {
        // default do nothing ，可由子类扩展重写
        // 比如写操作日志，发送MQ消息等等
    }

    private Workbook processToWorkBook(K request, T data, ExportExcelDataAction<T> exportExcelDataAction) {
        if (data instanceof Collection) {
            List<T> dataList = new ArrayList<>((Collection<T>) data);
            return exportExcelDataAction.getWorkbook(request.getExcelTitleText(), dataList);
        }
        return exportExcelDataAction.getWorkbook(request.getExcelTitleText(), Collections.singletonList(data));
    }

    /**
     * 导出Excel
     *
     * @param response 响应
     */
    protected void exportExcel(K request, HttpServletResponse response) throws IOException {
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("UTF-8");
        String fileName = URLEncoder.encode(request.getExcelTitleText(), "UTF-8");
        response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx");
        exportDetails(request);
    }

    /**
     * 获取类属性上的指定注解属性值
     *
     * @param clazz 目标类Class对象
     * @return 属性列表
     */
    private List<Field> getAnnotationValues(Class<?> clazz) {
        return Arrays.stream(clazz.getDeclaredFields())
                .filter(field -> field.isAnnotationPresent(ExcelProperties.class))
                .sorted(Comparator.comparingInt(field -> field.getAnnotation(ExcelProperties.class).index()))
                .collect(Collectors.toList());

    }

    private void doExport(Workbook workbook, OutputStream os) {
        try {
            // 写入输出流
            workbook.write(os);
        } catch (Exception e) {
            throw new RuntimeException("导出异常");
        } finally {
            try {
                workbook.close();
                os.close();
            } catch (IOException e) {
                // 关闭异常处理
            }
        }
    }

    /**
     * 创建普通Excel报表
     *
     * @param titleText     标题
     * @param excelDataList 数据列表
     */
    private Workbook createNormalExcelReport(String titleText, List<T> excelDataList) {
        int startRow = 0, maxWidth = 0;
        if (Objects.isNull(excelDataList) || excelDataList.isEmpty()) {
            return null;
        }
        SXSSFWorkbook workbook = new SXSSFWorkbook();
        try {
            SXSSFSheet sheet = workbook.createSheet();

            List<Field> annotationValues = getAnnotationValues(excelDataList.get(0).getClass());
            System.out.println(annotationValues);
            CellStyle headerStyle = createHeaderStyle(workbook);
            if (Objects.nonNull(titleText)) {
                // 创建合并区域（参数：起始行, 结束行, 起始列, 结束列）
                sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, annotationValues.size() - 1));
                Row titleRow = sheet.createRow(0);
                Cell titleCell = titleRow.createCell(0);
                titleCell.setCellValue(titleText);
                titleCell.setCellStyle(headerStyle);
                startRow++;
            }
            int maxGroupDepth = getMaxGroupDepth(annotationValues);
            // 创建多级表头
            for (int i = 0; i < maxGroupDepth; i++) {
                createMultiLevelHeaders(sheet, sheet.createRow(startRow), annotationValues, startRow, headerStyle, i,
                                        maxWidth);
                startRow++;
            }

            // 创建普通表头行（最后一层）
            Row headerRow = sheet.createRow(startRow);
            for (int i = 0; i < annotationValues.size(); i++) {
                Field field = annotationValues.get(i);
                ExcelProperties prop = field.getAnnotation(ExcelProperties.class);
                int valueLessDepth = startRow;
                while (valueLessDepth > 0) {
                    if (getValue(sheet, valueLessDepth - 1, i) == null) {
                        valueLessDepth--;
                    } else {
                        break;
                    }
                }
                Cell cell;
                // 定位
                if (valueLessDepth != startRow) {
                    sheet.addMergedRegion(new CellRangeAddress(valueLessDepth, startRow, i, i));
                    cell = sheet.getRow(valueLessDepth).createCell(i);
                } else {
                    cell = headerRow.createCell(i);
                }
                cell.setCellValue(prop.value());
                cell.setCellStyle(headerStyle);
            }
            // 设置合并边框
            setRegionBorder(sheet);

            // 填充数据行
            CellStyle dataStyle = createDataStyle(workbook);
            for (int rowNum = startRow; rowNum < excelDataList.size() + startRow; rowNum++) {
                T item = excelDataList.get(rowNum - startRow);
                Row dataRow = sheet.createRow(rowNum + 1);

                for (int colNum = 0; colNum < annotationValues.size(); colNum++) {
                    Field field = annotationValues.get(colNum);
                    field.setAccessible(true);
                    Object value = field.get(item);
                    Cell cell = dataRow.createCell(colNum);

                    ExcelProperties annotation = field.getAnnotation(ExcelProperties.class);
                    // 处理字典数据
                    if (StringUtils.isNotBlank(annotation.dicData())) {
                        String dictLabel = getDictLabel(annotation.dicData(), value);
                        cell.setCellValue(dictLabel);
                    } else if (StringUtils.equals(field.getName(), "seq")) {
                        cell.setCellValue(rowNum - startRow + 1);
                    } else {
                        // 默认值
                        setCellValue(cell, value, annotation);
                    }
                    cell.setCellStyle(dataStyle);
                }
            }
            return workbook;

        } catch (Exception e) {
            throw new RuntimeException("Excel导出失败", e);
        }
    }

    /**
     * 设置合并单元格边框
     *
     * @param sheet 表格
     */
    private void setRegionBorder(Sheet sheet) {
        sheet.getMergedRegions().forEach(region -> {
            BorderStyle bordertemp = BorderStyle.THIN;
            RegionUtil.setBorderTop(bordertemp, region, sheet);
            RegionUtil.setBorderRight(bordertemp, region, sheet);
            RegionUtil.setBorderBottom(bordertemp, region, sheet);
            RegionUtil.setBorderLeft(bordertemp, region, sheet);
        });
    }

    private String getValue(Sheet sheet, int row, int column) {
        if (Objects.nonNull(sheet.getRow(row)) && sheet.getRow(row).getCell(column) != null) {
            return sheet.getRow(row).getCell(column).getStringCellValue();
        }
        if (getMergeValue(sheet, row, column) != null) {
            return getMergeValue(sheet, row, column);
        }
        return null;
    }

    /**
     * 判断单元格是否被合并
     *
     * @param sheet  表格
     * @param row    行
     * @param column 列
     * @return 合并单元格值
     */
    private String getMergeValue(Sheet sheet, int row, int column) {
        //获取合并单元格的总数，并循环每一个合并单元格，
        int sheetMergeCount = sheet.getNumMergedRegions();
        for (int i = 0; i < sheetMergeCount; i++) {
            CellRangeAddress range = sheet.getMergedRegion(i);
            int firstColumn = range.getFirstColumn();
            int lastColumn = range.getLastColumn();
            int firstRow = range.getFirstRow();
            int lastRow = range.getLastRow();
            //判断当前单元格是否在合并单元格区域内，是的话就是合并单元格
            if ((row >= firstRow && row <= lastRow) && (column >= firstColumn && column <= lastColumn)) {
                Row fRow = sheet.getRow(firstRow);
                Cell fCell = fRow.getCell(firstColumn);
                //获取合并单元格首格的值
                return fCell.getStringCellValue();
            }
        }
        //非合并单元格个返回空
        return null;
    }

    /**
     * 获取最大分组深度
     *
     * @param fields 字段
     * @return 深度
     */
    private int getMaxGroupDepth(List<Field> fields) {
        return fields.stream()
                .map(field -> field.getAnnotation(ExcelProperties.class).group().length)
                .max(Integer::compareTo)
                .orElse(0);
    }

    /**
     * 构建多级表头
     *
     * @param fields 字段
     * @return 表头
     */
    private List<List<String>> buildHeaderGroups(List<Field> fields) {
        return fields.stream().map(field -> {
                    ExcelProperties annotation = field.getAnnotation(ExcelProperties.class);
                    return new ArrayList<>(Arrays.asList(annotation.group()));
                }).distinct().sorted(Comparator.comparing(List::toString)) // 按层级排序
                .collect(Collectors.toList());
    }

    /**
     * 创建多级表头
     *
     * @param sheet       表格
     * @param headerRow   表头行
     * @param fields      字段
     * @param startRow    开始行
     * @param headerStyle 表头样式
     * @param depth       层级
     * @param maxWidth    最大宽度
     */
    private void createMultiLevelHeaders(SXSSFSheet sheet, Row headerRow, List<Field> fields, int startRow,
                                         CellStyle headerStyle, int depth, int maxWidth) {
        List<List<String>> groups = buildHeaderGroups(fields);
        Map<String, CellRangeAddress> mergedRegions = new LinkedHashMap<>();

        for (int i = 0; i < fields.size(); i++) {
            Field field = fields.get(i);
            ExcelProperties annotation = field.getAnnotation(ExcelProperties.class);

            List<String> path = new ArrayList<>(Arrays.asList(annotation.group()));
            for (int level = depth; level < path.size(); level++) {
                String headerName = path.get(level);
                int currentRow = startRow;
                Row row = sheet.getRow(currentRow) == null ? sheet.createRow(currentRow) : sheet.getRow(currentRow);
                Cell cell = row.getCell(i);

                if (cell == null) {
                    cell = row.createCell(i);
                    cell.setCellValue(headerName);
                    cell.setCellStyle(headerStyle);
                }

                if (level <= path.size() - 1) {
                    // 合并相同父级的列
                    String parent = String.join("->", path.subList(0, level + 1));
                    int endCol = findEndColumn(fields, i, path, level);
                    if (endCol > i) {
                        // 创建合并区域（参数：起始行, 结束行, 起始列, 结束列）
                        mergedRegions.put(parent + "@" + currentRow + "-" + i,
                                          new CellRangeAddress(currentRow, currentRow, i, endCol));
                        i = endCol; // 跳过已合并列
                    }
                    // 最宽
                    if (maxWidth < endCol) {
                        maxWidth = endCol;
                    }

                }
            }
            if (StringUtils.equals(field.getName(), "seq")){
                sheet.setColumnWidth(i, 5 * 256);
            }else {
                sheet.setColumnWidth(i, annotation.width() * 256);
            }
        }

        // 添加合并区域
        mergedRegions.values().forEach(sheet::addMergedRegion);
    }

    /**
     * 查找当前层级下需要合并的结束列
     *
     * @param fields   字段
     * @param startCol 开始列
     * @param path     路径
     * @param level    层级
     * @return 结束列
     */
    private static int findEndColumn(List<Field> fields, int startCol, List<String> path, int level) {
        String target = String.join(":", path.subList(0, level + 1));
        for (int i = startCol + 1; i < fields.size(); i++) {
            ExcelProperties annotation = fields.get(i).getAnnotation(ExcelProperties.class);
            List<String> currentPath = new ArrayList<>(Arrays.asList(annotation.group()));
            if (currentPath.size() <= level || !String.join(":", currentPath.subList(0, level + 1)).equals(target)) {
                return i - 1;
            }
        }
        return fields.size() - 1;
    }

    /**
     * 创建表头样式
     *
     * @param workbook 工作簿
     * @return 样式
     */
    private static CellStyle createHeaderStyle(SXSSFWorkbook workbook) {
        CellStyle style = workbook.createCellStyle();
        Font font = workbook.createFont();
        font.setBold(true);
        font.setFontHeightInPoints((short) 12);
        style.setFont(font);
        style.setAlignment(HorizontalAlignment.CENTER);
        style.setVerticalAlignment(VerticalAlignment.CENTER);
        style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
        style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        style.setBorderBottom(BorderStyle.THIN);
        style.setBorderTop(BorderStyle.THIN);
        style.setBorderLeft(BorderStyle.THIN);
        style.setBorderRight(BorderStyle.THIN);
        return style;
    }

    /**
     * 创建数据样式
     *
     * @param workbook 工作簿
     * @return 样式
     */
    private static CellStyle createDataStyle(Workbook workbook) {
        CellStyle style = workbook.createCellStyle();
        style.setAlignment(HorizontalAlignment.LEFT);
        style.setVerticalAlignment(VerticalAlignment.CENTER);
        style.setBorderBottom(BorderStyle.THIN);
        style.setBorderTop(BorderStyle.THIN);
        style.setBorderLeft(BorderStyle.THIN);
        style.setBorderRight(BorderStyle.THIN);
        return style;
    }

    /**
     * 设置单元格值
     *
     * @param cell       单元格
     * @param value      值
     * @param annotation 注解
     */
    private static void setCellValue(Cell cell, Object value, ExcelProperties annotation) {
        if (value == null) {
            cell.setCellValue("");
            return;
        }
        if (value instanceof Date && StringUtils.isNotBlank(annotation.format())) {
            SimpleDateFormat sdf = new SimpleDateFormat(annotation.format());
            cell.setCellValue(sdf.format((Date) value));
        } else if (value instanceof Number) {
            cell.setCellValue(((Number) value).doubleValue());
        } else {
            cell.setCellValue(value.toString());
        }
    }

    /**
     * 获取字典对应值
     *
     * @param dicData 字典数据
     * @param value   值
     * @return 字典值
     */
    private static String getDictLabel(String dicData, Object value) {
        if (StringUtils.isBlank(dicData) || value == null) {
            return String.valueOf(value);
        }
        Map<Object, String> dictMap = Arrays.stream(dicData.split(";"))
                .filter(StringUtils::isNotBlank)
                .distinct()
                .map(dic -> dic.split(":"))
                .collect(Collectors.toMap(dic -> dic[0], dic -> dic[1]));
        return dictMap.getOrDefault(value, String.valueOf(value));
    }
}
